package com.zy.smarthome.frame.util;

import com.zy.smarthome.common.content.RedisKey;
import com.zy.smarthome.common.util.DistributedLink;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * 进行数据检查，看是否正确
 * 正确进行逻辑处理，错误查看是否有断帧情况,如果有进行断帧处理，如果没有则删除
 *
 * @author wjming
 */
@Component
@Slf4j(topic = "DataParse")
public class Parse2 {

    @Value("${topic.rabbit.smartHomeFrameProcess}")
    private String smartHomeFrameProcess;

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 数据帧长度必须小于2048个字节
     */
    private static final int DATA_LENGTH = 2048;
    private static final String DATA_HEAD = "2A";
    private static final String DATA_END = "23";
    /**
     * 数据中数据帧长度为255说明是FF
     */
    private static final int FULL_FRAME_LENGTH = 255;
    /**
     * 错误数据过滤
     */
    private static final String ERROR_DATA_GATEWAY = "41542B434F4E4E4543540D0A";
    /**
     * 数据正长度最少是16
     */
    private static final int FRAME_LENGTH_16 = 16;

    /**
     * 心跳帧
     */
    private static final String HEART = "1392";
    /**
     * 注册帧
     */
    private static final String REGISTER = "139C";

    /**
     * FIXME: 需要先找2A从第一个2A开始找下面的数据
     *
     * @Description: 检查数据帧如果正确放到队列之中
     * @param: data
     * @param: ChannelLongId
     * @param: ctx
     * @return: java.lang.Boolean
     * @author wjming
     * @Date 2021/1/25 10:38
     */
    public ChannelGroup checkAndSend(String data, String channelLongId, ChannelHandlerContext ctx, ChannelGroup channelGroup) {
        DistributedLink.createId();
        log.info("{} [ReceiveData] -> [{}]", this.getClass().getName(), data);
        String key = RedisKey.dataSplicing(channelLongId);
        data = data.trim();
        //如果数据包大于2048，数据包丢弃，说明不是我们的数据包
        if (data.length() > DATA_LENGTH) {
            return channelGroup;
        } else {
            Object dataObject = redisTemplate.opsForValue().get(key);
            if (dataObject != null) {
                if (StringUtils.startsWith(dataObject.toString(), DATA_HEAD)) {
                    data = dataObject + data;
                }
                redisTemplate.delete(key);
            }
            //以2A开头
            if (StringUtils.startsWith(data.trim(), DATA_HEAD)) {
                channelGroup = dataDealFun(data, key, channelLongId, ctx, channelGroup);
            }
            if (StringUtils.equals(data.trim(), ERROR_DATA_GATEWAY)) {
                return channelGroup;
            }
        }
        return channelGroup;
    }

    /**
     * @Description:数据帧处理找到第一个2A和23，截取这一段数据，检验数据是否正确，如果正确放到队列中，错误丢弃开始下一段
     * @param: data
     * @return: void
     * @author:wjming
     * @Date:2021/2/4 14:56
     */
    private ChannelGroup dataDealFun(String data, String key, String ChannelLongId, ChannelHandlerContext ctx, ChannelGroup channelGroup) {
        int deadlock = 0;
        do {
            deadlock = deadlock + 1;
            String frameAll;
            //1:一定从第一个2A开始找 如果为-1说明没有， 如果是0说明2A在第一位
            int twoA = StringUtils.indexOf(data, DATA_HEAD);
            if (twoA != -1) {
                //防止只有一个2A的情况
                if (DATA_HEAD.equals(data)) {
                    redisTemplate.opsForValue().set(key, data, 2, TimeUnit.SECONDS);
                    return channelGroup;
                }

                //0+2,0 + 4,0 + 6
                int twoA2 = twoA + 2, twoA4 = twoA + 4, twoA6 = twoA + 6;

                //获取数据2A后面的第2-4位数据帧长度数据 并且转化为十进制数据长度
                int dataLength = Integer.parseInt(StringUtils.substring(data, twoA2, twoA4), 16);
                //安全控制:防止数据循环，数据长度必须大于16位，因为最少的MAC地址就已经有16位了
                int length = dataLength * 2;

                if (length < FRAME_LENGTH_16) {
                    return channelGroup;
                }
                //如果2-4位是FF，说明是多MAC数据
                if (dataLength == FULL_FRAME_LENGTH) {

                    //数据帧中的数据长度
                    dataLength = dataLength + Integer.parseInt(StringUtils.substring(data, twoA4, twoA6), 16);
                    //总数据长度 即23+FF+dataLength+校验和+23
                    int allDataLength = dataLength * 2 + 10;

                    //dataLength * 2目的是计字符串长度,+10指的是2A，4位数据长度，+2为校验位+23(双字节数据长度数据)
                    if (data.length() >= allDataLength) {
                        //加上2A不知道后面有没有23后面验证，但是至少需要按照数据长度截取
                        int needCutDataLength = dataLength * 2 + twoA + 10;
                        //截取后的真实业务数据的的开始,真实业务数据的结束
                        int realDataStart = twoA + 6, realDataEnd = twoA + 6 + dataLength * 2;
                        //原始数据的校验和开始和结束
                        int dataCmdBodySumStart = twoA + 6 + dataLength * 2, dataCmdBodySumEnd = twoA + 6 + dataLength * 2 + 2;

                        //可以发送的真确数据
                        frameAll = StringUtils.substring(data, twoA, needCutDataLength);
                        //截取dataLength所显示的数据长度，并校对校验和
                        //没有加上2A和23数据长度的数据
                        String frameNo2A23AndDataLength = StringUtils.substring(frameAll, realDataStart, realDataEnd);

                        //计算校验和
                        String cmdBodySum = CmdUtils.getCmdBodySum(frameNo2A23AndDataLength);
                        //判断校验和是否正确
                        if (StringUtils.equals(cmdBodySum, StringUtils.substring(data, dataCmdBodySumStart, dataCmdBodySumEnd))) {
                            //说明是一条正确数据
                            data = StringUtils.removeStart(data, StringUtils.substring(data, 0, twoA) + frameAll);
                            channelGroup = dataSendAndSetRelation(frameAll, ChannelLongId, ctx, channelGroup);
                        }
                    } else {
                        //至少是有数据帧头和数据帧长度的多MAC地址的数据
                        log.warn("双节残余数据:{}", data);
                        if (StringUtils.startsWith(data, DATA_HEAD)) {
                            redisTemplate.opsForValue().set(key, data, 2, TimeUnit.SECONDS);
                        }
                        data = "";
                    }
                } else {
                    //数据长度即:2A+数据长度+校验和+23 的长度
                    int frameDataLength = dataLength * 2 + 8;

                    //单字节数据
                    if (data.length() >= frameDataLength) {
                        //需要截取的数据长度
                        int frameEnd = dataLength * 2 + twoA + 8;
                        //截取后的真实业务数据的校验和开始和结束
                        int realDataStart = twoA + 4, realDataStartEnd = twoA + 4 + dataLength * 2;
                        //原始数据的校验和开始和结束
                        int dataCmdBodySumStart = twoA + 4 + dataLength * 2, dataCmdBodySumEnd = twoA + 4 + dataLength * 2 + 2;

                        //截取dataLength所显示的数据长度，并校对校验和
                        //加上2A不知道后面有没有23后面验证，但是至少需要按照数据长度截取
                        frameAll = StringUtils.substring(data, twoA, frameEnd);
                        //没有加上2A和23的数据
                        String frame = StringUtils.substring(frameAll, realDataStart, realDataStartEnd);

                        //计算校验和
                        String cmdBodySum = CmdUtils.getCmdBodySum(frame);
                        //判断校验和是否正确
                        String cmdBodySumSrc = StringUtils.substring(data, dataCmdBodySumStart, dataCmdBodySumEnd);
                        if (StringUtils.equals(cmdBodySum, cmdBodySumSrc) && StringUtils.startsWith(frameAll.trim(), DATA_HEAD) && StringUtils.endsWith(frameAll.trim(), DATA_END)) {
                            //说明是一条正确数据
                            data = StringUtils.removeStart(data, StringUtils.substring(data, 0, twoA) + frameAll);
                            channelGroup = dataSendAndSetRelation(frameAll, ChannelLongId, ctx, channelGroup);
                        }
                    } else {
                        //至少是有数据帧头和数据帧长度的单MAC地址数据
                        log.warn("单字节残余数据:{}", data);
                        if (StringUtils.startsWith(data, DATA_HEAD)) {
                            redisTemplate.opsForValue().set(key, data, 2, TimeUnit.SECONDS);
                        }
                        data = "";
                    }
                }

            }
        }
        while (data.length() > 0 && deadlock < 50);
        if (deadlock >= 50) {
            log.info("{} [ErrorData(50)] -> [{}]", this.getClass().getName(), data);
        }
        return channelGroup;
    }

    /**
     * 发送数据并且建立关系
     *
     * @param frameAll
     * @param channelLongId
     * @param ctx
     */
    private ChannelGroup dataSendAndSetRelation(String frameAll, String channelLongId, ChannelHandlerContext ctx, ChannelGroup channelGroup) {
        String clientIp = "";
        Map<String, Object> frameAndChannel = new HashMap<>(3);
        frameAndChannel.put("frame", frameAll);
        frameAndChannel.put("channelLongId", RedisKey.gatewayChannelRelation(channelLongId));
        frameAndChannel.put("traceId", DistributedLink.getId());
        //如果是登陆帧则需要把mac和channel longId之间建立联系
        String od = StringUtils.substring(FrameDataDeal.getData(frameAll), 18, 22);

        if (StringUtils.equals(od, HEART) || StringUtils.equalsIgnoreCase(od, REGISTER)) {
            try {
                InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
                clientIp = ipSocket.getAddress().getHostAddress();
            } catch (Exception e) {
                log.warn("获取用户Ip地址失败:{}", ctx.channel().id());
            }

            Map<String, Object> map = new HashMap<>(2);
            String gatewayMac = StringUtils.substring(FrameDataDeal.getData(frameAll), 2, 18);
            map.put("gatewayMac", gatewayMac);
            map.put("channelId", ctx.channel().id());
            map.put("clientIp", clientIp);
            redisTemplate.opsForValue().set(RedisKey.gatewayChannelRelation(channelLongId), map, 20, TimeUnit.MINUTES);

            Object channelLid = redisTemplate.opsForValue().get(gatewayMac + "_channel");
            if (channelLid != null && !StringUtils.equals((String) channelLid, RedisKey.gatewayChannelRelation(channelLongId))) {
                redisTemplate.delete(channelLid);

                redisTemplate.delete(RedisKey.heartbeat(StringUtils.substringAfterLast((String) channelLid, ".")));

                Object gatewayChannelRelation = redisTemplate.opsForValue().get(channelLid);
                if (gatewayChannelRelation != null) {
                    Map<String, Object> gatewayAndChannelRelation = (Map<String, Object>) gatewayChannelRelation;
                    ChannelId channelId = (ChannelId) gatewayAndChannelRelation.get("channelId");
                    Channel channel1 = channelGroup.find(channelId);
                    if (channel1 != null) {
                        channel1.close();
                        channelGroup.remove(channel1);
                    }
                }
            }
            rabbitTemplate.convertAndSend(smartHomeFrameProcess, frameAndChannel);
            return channelGroup;
        }

        rabbitTemplate.convertAndSend(smartHomeFrameProcess, frameAndChannel);
        return channelGroup;
    }
}

